package com.nf.mvc.exception;

import com.nf.mvc.HandlerExceptionResolver;
import com.nf.mvc.handler.HandlerMethod;

import java.lang.reflect.Method;
import java.util.List;
import java.util.function.Predicate;

/**
 * 本异常解析器支持异常处理方法通过注解{@link ExceptionHandlers}指定能处理多个异常的情况，
 * 使用情况如下：
 * <pre class="code">
 *     &#64;ExceptionHandlers({AException.class,BException.class})
 *     public void handleExaAndExb(Exception ex){
 * <p>
 *     }
 * </pre>
 * <p><b>注意:异常处理方法的参数仍然只能有一个异常类型的参数，而且此异常参数必须是注解指定的异常或者是其父类型,
 * 通常情况下异常参数会是注解指定的各个异常的共同父类型,但此异常解析器并不强制要求这一点,
 * 合理的实现见{@link com.nf.mvc.util.ClassUtils#isAssignableAny(Class, Class[])}</b></p>
 *
 * <p>如果想让异常处理方法除了支持异常类型作为参数,还支持类似于控制器方法的参数类型,
 * 那么就需要使用{@link ParameterizedExceptionHandlersExceptionResolver}</p>
 *
 * <p>此类并没有直接使用在框架中,而是用了其子类{@link ParameterizedExceptionHandlersExceptionResolver}</p>
 *
 * @see com.nf.mvc.DispatcherServlet
 * @see HandlerExceptionResolver
 * @see ExceptionHandlers
 * @see ExceptionHandlerExceptionResolver
 * @see ParameterizedExceptionHandlersExceptionResolver
 */
public class ExceptionHandlersExceptionResolver extends ExceptionHandlerExceptionResolver {

    /**
     * 这样重写之后，相当于只有scan功能，没有添加后置处理的功能，等价于把方法{@link #postHandleExceptionHandlerMethods(List)}
     * 重写为一个空的方法.
     * <p>你如果只重写{@link #scanExceptionHandlerMethods(Predicate)},
     * 而不直接重写resolveExceptionHandlerMethods方法，比如下面的写法
     * <pre class="code">
     *  protected void scanExceptionHandlerMethods(Predicate<Method> predicate) {
     *      super.scanExceptionHandlerMethods(method -> method.isAnnotationPresent(ExceptionHandlers.class));
     *  }
     * </pre>
     * 那么就相当于你仍然使用父类的后置处理功能,但父类的后置处理功能并不适用于本类</p>
     * <p>总而言之:由于本类要改变扫描逻辑以及不需要的后置处理能力,其它能力与父类保持一致,所以重写方案有
     * <ul>
     *     <li>重写本方法与{@link #isExceptionHandleMethod(Method)},方法里面调用{@link #scanExceptionHandlerMethods(Predicate)},
     *     扫描方法的参数指定为{@link #isExceptionHandleMethod(Method)}</li>
     *     <li>重写本方法,里面调用{@link #scanExceptionHandlerMethods(Predicate)},并在调用扫描方法时传递确定异常处理方法的lambda表达式</li>
     *     <li>重写{@link #postHandleExceptionHandlerMethods(List)}为空方法,并重写{@link  #isExceptionHandleMethod(Method)}</li>
     * </ul>
     * 这里采用了方案二的重写方式,违反了父类中的重写建议:不建议重写resolveExceptionHandlerMethods,
     * 这是为了知识演示用,建议用第三种重写方案</p>
     * <p><b><i>注意:这里没有处理挂羊头卖狗肉的问题,要处理你可以重写{@link #isExceptionHandleMethod(Method)}方法,
     * 并此方法重写替换掉扫描方法的lambda表达式</i></b></p>
     *
     * <p><b><i>为什么不需要后置处理?</i></b>这是因为此异常解析器的每一个异常处理方法可以处理多个异常,
     * 有可能出现异常处理方法1和2都可以处理异常A与异常B,产生A异常不是异常处理方法1去处理执行,
     * 但产生B异常却是异常处理方法1去处理执行,初始化阶段进行排序是不可能的,
     * 只有在执行链执行过程中产生了异常,然后利用这个异常去找合适的异常处理方法才有意义</p>
     */
    @Override
    protected void resolveExceptionHandlerMethods() {
        scanExceptionHandlerMethods(method -> method.isAnnotationPresent(ExceptionHandlers.class));
    }

    @Override
    protected HandlerMethod findMostMatchedHandlerMethod(List<HandlerMethod> handlerMethods, Exception exception) {
        HandlerMethod mostMatchedHandlerMethod = null;
        Class<?> mostMatchedExceptionClass = null;
        for (HandlerMethod exHandleMethod : handlerMethods) {
            Method method = exHandleMethod.getMethod();
            Class<? extends Exception>[] exceptionClasses = method.getDeclaredAnnotation(ExceptionHandlers.class)
                    .value();
            Class<?> matchedExceptionClass;
            for (Class<? extends Exception> exceptionClass : exceptionClasses) {
                if (exceptionClass.isAssignableFrom(exception.getClass())) {
                    matchedExceptionClass = exceptionClass;
                    if (mostMatchedExceptionClass == null) {
                        mostMatchedExceptionClass = matchedExceptionClass;
                    }
                    if (mostMatchedExceptionClass.isAssignableFrom(matchedExceptionClass)) {
                        mostMatchedExceptionClass = matchedExceptionClass;
                        mostMatchedHandlerMethod = exHandleMethod;
                    }
                }
            }
        }
        return mostMatchedHandlerMethod;
    }
}
